#ifndef STE_CLASSES_H
#define STE_CLASSES_H

#include "SymTab.h"
#include "SymTabEntry.h"
#include "Ast.h"

class StmtNode;
class RuleNode;
class ExprNode;
class PatNode;
class VariableEntry;
class OpNode;
class PrimitivePatNode;
extern string newName(const string&);

/****************************************************************
  The first few classes dont really add any functionality over
  the base class, except for providing a convenient constructor.
****************************************************************/

class GlobalEntry: public SymTabEntry {
 public:
  GlobalEntry(string name, int line=0, int column=0, string file=" "):
    SymTabEntry(name, SymTabEntry::GLOBAL_KIND, line, column,file), rules_() {};
  ~GlobalEntry() {};

  const vector<RuleNode*> rules() const { return rules_;};
  vector<RuleNode*> rules() { return rules_;};
  const RuleNode* rule(int i) const { return rules_[i];}
  RuleNode* rule(int i) { return rules_[i];}
  void addRule(RuleNode* re) { rules_.push_back(re);};

  const Type* typeCheck();
  void print(ostream&, int indent=0) const;
  void typePrint(ostream&, int indent=0) const;
  ImmCodes* code(bool controlFlow=false);

 private:
  vector<RuleNode*> rules_;
};

class BlockEntry: public SymTabEntry {
 public:
  BlockEntry(string name, int line=0, int column=0, string file=""):
    SymTabEntry(name, SymTabEntry::BLOCK_KIND, line, column, file, (Type*)&Type::voidType) {};
  ~BlockEntry() {};

  const Type* typeCheck();
  void print(ostream& out, int indent=0) const;
  void typePrint(ostream&, int indent=0) const;
};

class RuleBlockEntry: public BlockEntry {
 public:
  RuleBlockEntry(int line=0, int column=0, string file=""):
    BlockEntry(newName("rule"), line,column, file) { kind(RULE_BLOCK_KIND);};
  ~RuleBlockEntry() {};
  ImmCodes* code(bool controlFlow=false);
};

/****************************************************************
  Following classes add more significant functionality to that
  provided by SymTabEntry.
****************************************************************/

class VariableEntry: public SymTabEntry {
 public:
  enum VarKind {GLOBAL_VAR, LOCAL_VAR, PARAM_VAR, UNDEFINED};

 public:
  VariableEntry(string name, VarKind v, Type* type=NULL,
				ExprNode* init= NULL, int ln=0, int col=0, string file=""):
    SymTabEntry(name, SymTabEntry::VARIABLE_KIND, ln, col, file, type) {
    vkind_ = v; initVal(init);
  };

  VariableEntry(const VariableEntry &v);
  ~VariableEntry() {};

  VarKind varKind() const { return vkind_;};
  void varKind(VarKind v) { vkind_ = v;};

  int offSet() const { return offSet_;} ;
  void offSet(int o) {offSet_ = o;};

  const ExprNode* initVal() const { return initVal_;}
  ExprNode* initVal() { return initVal_;};
  void initVal(ExprNode *init) { initVal_ = init;};

  const Type* typeCheck();
  void print(ostream& os, int indent=0) const;
  ImmCodes* code(bool controlFlow=false);
  string registerName();
  void registerName(string name) {reg_name = name;}

  void typePrint(ostream&, int indent=0) const;

 private:
  VarKind vkind_;
  int offSet_;
  ExprNode* initVal_;
  // CodeGen
  string reg_name; /* register name */
};

class ClassEntry: public SymTabEntry {
 public:
  ClassEntry(string name, int line=0, int column=0, string file="")
    : SymTabEntry(name, SymTabEntry::CLASS_KIND, line,column, file) {};
  ~ClassEntry() {};

  const Type* typeCheck();
  void print(ostream& os, int indent) const;
  void typePrint(ostream&, int indent=0) const;
};

class FunctionEntry: public SymTabEntry {
 public:
  FunctionEntry(string name, Type* type=NULL,
				int line=0, int column=0, string file=""):
    SymTabEntry(name, SymTabEntry::FUNCTION_KIND, line,column, file, type) {
      body_ = NULL;
	  formals_ = NULL;
	  hasRecursion_ = false;
 };
  ~FunctionEntry() {};

  void initFormals() {};
  const CompoundStmtNode* body() const { return body_;};
  CompoundStmtNode* body() {return body_;};
  void body(CompoundStmtNode* n) { body_ = n;};

  vector<const VariableEntry*>* formals() { return formals_; };
  const vector<const VariableEntry*>* formals() const { return formals_; };

  bool hasRecursion() const { return hasRecursion_;} ;
  void hasRecursion(bool hasRecursion) {hasRecursion_ = hasRecursion;};

  const Type* typeCheck();
  void print(ostream& os, int indent) const;
  ImmCodes* code(bool controlFlow=false);
  void typePrint(ostream&, int indent=0) const;

 private:
  CompoundStmtNode* body_;
  vector<const VariableEntry*>* formals_; // initialized in typecheck()
  bool hasRecursion_; // added flag for recursion

};

class EventEntry: public SymTabEntry {
 public:
  EventEntry(string name, int line=0, int column=0, string file=" "):
    SymTabEntry(name, SymTabEntry::EVENT_KIND, line,column, file) {};
  ~EventEntry() {};

  const Type* typeCheck();
  void print(ostream& out, int indent=0) const;
  void typePrint(ostream&, int indent=0) const;
};

/* WhileLoopEntry class added */
class WhileLoopEntry: public SymTabEntry {
	public:
		WhileLoopEntry(string name,
				int line=0, int column=0, string file=""):
			SymTabEntry(name, SymTabEntry::WHILE_LOOP_KIND, line,column, file) {
				body_ = NULL;
			};
		~WhileLoopEntry() {};

		const WhileNode* body() const { return body_;};
		WhileNode* body() {return body_;};
		void body(WhileNode* n) { body_ = n;};

		void print(ostream& os, int indent) const;
		void doLoopInvariant() {
		    if(!body())
                return;
            ostringstream os;
            StmtNode * st= body()->whileStmt();
            ExprNode* expr = body()->cond();
            expr->printVarOnly(os,0);
            //cout<< os.str()<<endl;
            //os.flush();
            string cond = os.str();
            int temp =0;
            vector<string> iv_var;
            for(int i=0;i<cond.length();i++) {
                if(cond[i] == ';') {
                iv_var.push_back(cond.substr(temp,i-temp));
                //cout<<cond.substr(temp,i);
                temp =i+1;
                }
            }
            cout<<endl;
            list<StmtNode*>* sm ;
            list<StmtNode*>* new_sm  = new list<StmtNode*>;
            list<StmtNode*>::iterator it;
            vector<StmtNode*>* invariant_stmt = new vector<StmtNode*>;


            if(st->stmtNodeKind() == StmtNode::COMPOUND) {
                sm = ((CompoundStmtNode*)st)->stmts();
                it = sm->begin();
                bool inv_flag = false;
                 for (;it!=sm->end();++it) {
                /* propagate Next */
                inv_flag=false;
                if((*it)->stmtNodeKind() == StmtNode::EXPR) {

                    ExprNode* et = ((ExprStmtNode*)(*it))->expr();
                    if(et->exprNodeType() == ExprNode::OP_NODE && ((OpNode*)et)->opCode() == OpNode::PRINT) {
                         new_sm->push_back(*it);
                        continue;
                    }


                    if(et->exprNodeType() == ExprNode::OP_NODE && ((OpNode*)et)->opCode() == OpNode::ASSIGN) {
                        ostringstream os;
                        int i;
                        temp=0;
                        if(((OpNode*)et)->arg(0))
                        ((OpNode*)et)->arg(0)->printVarOnly(os,0);
                        string left = os.str();
                        vector<string> left_var;
                        for(i=0;i<left.length();i++) {
                        if(left[i] == ';') {
                        left_var.push_back(left.substr(temp,i-temp));
                //cout<<cond.substr(temp,i);
                            temp =i+1;
                        }
                        }
                        temp =0;
                        ostringstream os2;
                        if(((OpNode*)et)->arg(1))
                        ((OpNode*)et)->arg(1)->printVarOnly(os2,0);
                        string right = os2.str();
                        vector<string>::iterator its;
                        for(i=0;i<right.length();i++) {
                        if(right[i] == ';') {
                            its = find(left_var.begin(),left_var.end(),right.substr(temp,i-temp));
                            if (its != left_var.end()) {

                                break;
                            }
                            temp =i+1;


                        }
                    }
                    if( i != right.length()) {
                        new_sm->push_back(*it);
                        continue;
                        }





                    }
                    ostringstream os;
                    et->printVarOnly(os,0);
                   //cout<< os.str()<<endl;
                     //os.flush();
                     temp =0;
                     string stmt_str = os.str();
                        int i;
                      vector<string>::iterator its;
                     for(i=0;i<stmt_str.length();i++) {
                        if(stmt_str[i] == ';') {
                            its = find(iv_var.begin(),iv_var.end(),stmt_str.substr(temp,i-temp));
                            if (its != iv_var.end())
                                break;
                            temp =i+1;


                        }
                    }
                    if( i == stmt_str.length()) {
                        invariant_stmt->push_back((*it));
                        inv_flag = true;
                        //list_sm[j] = NULL;
                        //((CompoundStmtNode*)st)->remove(*it);

                        //(*it)->print(cout,0);
                    }


                    //chekc for varibale in the cond if not present do loop invariant


                }
                if(!inv_flag)
                 new_sm->push_back(*it);

            }


                //if str list don't contain loop cond remove it from while


            }

            //if(st)
            //st->print(cout,0);
            if(new_sm->size() > 0)
            ((CompoundStmtNode*)st)->stmts(new_sm);
            if(invariant_stmt->size() > 0)
             body()->addLoopInvStmt(invariant_stmt);
            /*if(new_sm->size() > 0)
                ((CompoundStmtNode*)st)->stmts(new_sm);
            else {
                 new_sm->push_back(NULL);
                ((CompoundStmtNode*)st)->stmts(new_sm);
            }

            if(invariant_stmt->size() > 0)
                body()->addLoopInvStmt(invariant_stmt);
            else
                body()->addLoopInvStmt(NULL);*/
            /*if(invariant_stmt->size() == 0)
                return NULL;
            else
            return invariant_stmt;*/
        }

	private:
		WhileNode* body_;
};


#endif
